﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Security;
using System.Text;
using System.Threading;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

namespace PoshRabbit
{
    class RabbitConsumer : IDisposable, IRabbitConsumer
    {
        private readonly RabbitConnector _connector;
        private string _queue;
        private QueueingBasicConsumer _consumer;
        private bool _isRunning;
        readonly string _name;
        
        internal RabbitConsumer(RabbitConnector connector, string queue, string name )
        {
            _connector = connector;
            _queue = queue;
            _name = name;
        }

        public string Queue
        {
            get
            {
                return _queue;
            }
        }

        public void Dispose()
        {
            TearDownConsumer();
        }

        internal void Initialize()
        {
            _connector.CreateModel();

            if (null == _queue)
            {
                _queue = _connector.Model.QueueDeclare();
            }
            else
            {
                _connector.Model.QueueDeclare(_queue);
            }

            _connector.Model.QueueBind(_queue, _connector.Exchange, _connector.RoutingKey, true, null);

            _consumer = new QueueingBasicConsumer(_connector.Model);
        }
      
        internal void Stop()
        {
            TearDownConsumer();
        }

        internal void Start()
        {
            if( _isRunning )
            {
                return;
            }

            _isRunning = true;
        
            try
            {
                ExecuteConsumer();
            }
            finally
            {
                _isRunning = false;
            }
        }

        public string Name
        {
            get { return _name; }
        }

        public bool IsRunning
        {
            get { return _isRunning; }
        }

        public event EventHandler<MessageDataEventArgs> MessageDelivered;

        IModel Model { get { return _connector.Model; } }

        void TearDownConsumer()
        {
            var model = Model;
            if (null != model && model.IsOpen)
            {
                model.QueueUnbind(_queue, _connector.Exchange, _connector.RoutingKey, null);
                model.QueueDelete(_queue, true, false, true);

                _connector.DestroyModel();
            }
        }

        void ExecuteConsumer()
        {
            Model.BasicConsume(_queue, true, null, _consumer);

            while (true)
            {
                try
                {
                    DequeueNextMessage();
                }
                catch (OperationCanceledException)
                {
                    return;
                }
                catch (EndOfStreamException)
                {
                    return;
                }
                catch( Exception e )
                {
                    var record = new ErrorRecord(e, "PoshRabbit.ConsumerError", ErrorCategory.NotSpecified, this);
                    
                    return;
                }
            }                        
        }

        private void DequeueNextMessage()
        {
            BasicDeliverEventArgs basicDeliverEventArgs = null;
            
            object result = _consumer.Queue.Dequeue();
            
            basicDeliverEventArgs = result as BasicDeliverEventArgs;
            if (null == basicDeliverEventArgs)
            {
                return;
            }

            RaiseDeliveryEvent(basicDeliverEventArgs);
        }

        private void RaiseDeliveryEvent(BasicDeliverEventArgs basicDeliverEventArgs)
        {
            var ev = this.MessageDelivered;
            if( null != ev)
            {
                var pso = PSObject.AsPSObject(basicDeliverEventArgs);

                AssignMessagePropertyValue(basicDeliverEventArgs, pso);

                ev(this, new MessageDataEventArgs{MessageData = pso});
            }
        }

        private void AssignMessagePropertyValue(BasicDeliverEventArgs basicDeliverEventArgs, PSObject pso)
        {
            if( null != this._connector.MessageEncoding)
            {
                var message = _connector.MessageEncoding.GetString(basicDeliverEventArgs.Body);
                PSPropertyInfo member = new PSNoteProperty( "Message", message );
                pso.Properties.Add( member );
            }
        }
    }
}
